/* ========================================================================
 * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved.
 *
 * OPC Foundation MIT License 1.00
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * The complete license agreement can be found here:
 * http://opcfoundation.org/License/MIT/1.00/
 * ======================================================================*/

using System;
using System.IO;
using System.Text;

namespace Opc.Ua
{
    /// <summary>
    /// Extensions for encoders to encode or decode session-less messages.
    /// </summary>
    public static class SessionLessMessage
    {
        /// <summary>
        /// Decodes a session-less message from a json.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="buffer"/>
        /// is <c>null</c>.</exception>
        public static IEncodeable DecodeAsJson(
            byte[] buffer,
            IServiceMessageContext context)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException(nameof(buffer));
            }

            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            using var decoder = new JsonDecoder(Encoding.UTF8.GetString(buffer), context);
            // decode the actual message.
            var message = new SessionLessServiceMessage();
            message.Decode(decoder);
            return message.Message;
        }

        /// <summary>
        /// Encodes a session-less message to a buffer.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="message"/>
        /// is <c>null</c>.</exception>
        /// <exception cref="ServiceResultException"></exception>
        public static void EncodeAsJson(
            IEncodeable message,
            Stream stream,
            IServiceMessageContext context,
            bool leaveOpen)
        {
            if (message == null)
            {
                throw new ArgumentNullException(nameof(message));
            }

            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            // create encoder.
            var encoder = new JsonEncoder(context, true, false, stream, leaveOpen);
            try
            {
                long start = stream.Position;

                // write the message.
                var envelope = new SessionLessServiceMessage
                {
                    NamespaceUris = context.NamespaceUris,
                    ServerUris = context.ServerUris,
                    Message = message
                };

                envelope.Encode(encoder);

                // check that the max message size was not exceeded.
                if (context.MaxMessageSize > 0 &&
                    context.MaxMessageSize < (int)(stream.Position - start))
                {
                    throw ServiceResultException.Create(
                        StatusCodes.BadEncodingLimitsExceeded,
                        "MaxMessageSize {0} < {1}",
                        context.MaxMessageSize,
                        (int)(stream.Position - start));
                }

                encoder.Close();
            }
            finally
            {
                if (leaveOpen)
                {
                    stream.Position = 0;
                }
                encoder.Dispose();
            }
        }

        /// <summary>
        /// Decodes a session-less message from a buffer.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="buffer"/>
        /// is <c>null</c>.</exception>
        /// <exception cref="ServiceResultException"></exception>
        public static IEncodeable DecodeAsBinary(
            byte[] buffer,
            IServiceMessageContext context)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException(nameof(buffer));
            }

            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            using var decoder = new BinaryDecoder(buffer, context);
            // read the node id.
            NodeId typeId = decoder.ReadNodeId(null);

            // convert to absolute node id.
            var absoluteId = NodeId.ToExpandedNodeId(typeId, context.NamespaceUris);

            // lookup message session-less envelope type.
            Type actualType = decoder.Context.Factory.GetSystemType(absoluteId);

            if (actualType == null || actualType != typeof(SessionlessInvokeRequestType))
            {
                throw ServiceResultException.Create(
                    StatusCodes.BadDecodingError,
                    "Cannot decode session-less service message with type id: {0}.",
                    absoluteId);
            }

            // decode the actual message.
            var message = new SessionLessServiceMessage();

            message.Decode(decoder);

            decoder.Close();

            return message.Message;
        }

        /// <summary>
        /// Encodes a session-less message to a buffer.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="context"/>
        /// is <c>null</c>.</exception>
        /// <exception cref="ServiceResultException"></exception>
        public static void EncodeAsBinary(
            IEncodeable message,
            Stream stream,
            IServiceMessageContext context,
            bool leaveOpen)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            // create encoder.
            using var encoder = new BinaryEncoder(stream, context, leaveOpen);
            long start = encoder.Position;

            // write the type id.
            encoder.WriteNodeId(null, DataTypeIds.SessionlessInvokeRequestType);

            // write the message.
            var envelope = new SessionLessServiceMessage
            {
                NamespaceUris = context.NamespaceUris,
                ServerUris = context.ServerUris,
                Message = message ?? throw new ArgumentNullException(nameof(message))
            };

            envelope.Encode(encoder);

            // check that the max message size was not exceeded.
            if (context.MaxMessageSize > 0 &&
                context.MaxMessageSize < (int)(encoder.Position - start))
            {
                throw ServiceResultException.Create(
                    StatusCodes.BadEncodingLimitsExceeded,
                    "MaxMessageSize {0} < {1}",
                    context.MaxMessageSize,
                    (int)(encoder.Position - start));
            }
        }
    }
}
